/*
    SDL - Simple DirectMedia Layer
    Copyright (C) 1997-2009 Sam Lantinga

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

    Sam Lantinga
    slouken@libsdl.org
*/
#include "SDL_cellvideo.h"

/* Helpers */
static void							CenterAndScale				(GLint* aX, GLint* aY, GLint* aWidth, GLint* aHeight, uint32_t aImageWidth, uint32_t aImageHeight)
{
	float outputaspect = (float)aImageWidth / (float)aImageHeight;
	float imageaspect = (float)*aWidth / (float)*aHeight;

	if(outputaspect > imageaspect)
	{
		int32_t oldheight = *aHeight;
		*aHeight = (uint32_t)((float)*aWidth / outputaspect);
		*aY += (oldheight - *aHeight) / 2;
	}
	else
	{
		int32_t oldwidth = *aWidth;
		*aWidth = (uint32_t)((float)*aHeight * outputaspect);
		*aX += (oldwidth - *aWidth) / 2;
	}
}

static void							CalculateViewport			()
{
	GLint* viewport = CELL_Video.ScreenConfiguration.Viewport;

	viewport[0] = 0;
	viewport[1] = 0;
	viewport[2] = CELL_Video.ScreenConfiguration.Width;
	viewport[3] = CELL_Video.ScreenConfiguration.Height;

	CenterAndScale(&viewport[0], &viewport[1], &viewport[2], &viewport[3], CELL_Video.VideoMode.Width, CELL_Video.VideoMode.Height);

	float fwidth = (float)viewport[2];
	float fheight = (float)viewport[3];

	viewport[0] += (int32_t)(fwidth * ((float)(CELL_Video.ScreenConfiguration.UnderscanX) / 100.0f));
	viewport[2] -= (int32_t)(fwidth * ((float)(CELL_Video.ScreenConfiguration.UnderscanX) / 100.0f * 2));
	viewport[1] += (int32_t)(fheight * ((float)(CELL_Video.ScreenConfiguration.UnderscanY) / 100.0f));
	viewport[3] -= (int32_t)(fheight * ((float)(CELL_Video.ScreenConfiguration.UnderscanY) / 100.0f * 2));
}

static void							MakeAttribSetRect			(GLuint* aBufferID, GLuint* aAttributeSet, float aX, float aY, float aWidth, float aHeight, uint32_t aVerticalFlip)
{
	if(*aAttributeSet == 0)
	{
		glGenAttribSetsSCE(1, aAttributeSet);
	}

	uint32_t newbuffer = 0;
	if(*aBufferID == 0)
	{
		glGenBuffers(1, aBufferID);
		newbuffer = 1;
	}

	glBindAttribSetSCE(*aAttributeSet);
	glBindBuffer(GL_ARRAY_BUFFER, *aBufferID);

	GLfloat VertexBuffer[20];
	if(!aVerticalFlip)
	{
		CELL_PSGL_SetRectangle(VertexBuffer, aX, aY, aWidth, aHeight);
	}
	else
	{
		CELL_PSGL_SetVertex(VertexBuffer + 0,  aX,				aY + aHeight,	0.0f, 0.0f);
		CELL_PSGL_SetVertex(VertexBuffer + 5,  aX + aWidth,		aY + aHeight,	1.0f, 0.0f);
		CELL_PSGL_SetVertex(VertexBuffer + 10, aX + aWidth,		aY,				1.0f, 1.0f);
		CELL_PSGL_SetVertex(VertexBuffer + 15, aX,				aY,				0.0f, 1.0f);
	}

	if(newbuffer)
	{
		glBufferData(GL_ARRAY_BUFFER, sizeof(VertexBuffer), VertexBuffer, GL_STATIC_DRAW);
	}
	else
	{
		glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(VertexBuffer), VertexBuffer);
	}

	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);

	glVertexPointer(3, GL_FLOAT, 5 * sizeof(GLfloat), (GLvoid*)0);
	glTexCoordPointer(2, GL_FLOAT, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));

	glBindAttribSetSCE(0);
}

/* Exported helpers */
void				CELL_PSGL_MapTextureAsFrameBuffer			(GLuint aTextureID, uint32_t aWidth, uint32_t aHeight)
{
	glBindFramebufferOES(GL_FRAMEBUFFER_OES, CELL_Video.PSGLState.FrameBufferID);
	glFramebufferTexture2DOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, aTextureID, 0);

	glViewport(0, 0, aWidth, aHeight);
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	glOrthof(0, aWidth, aHeight, 0, -1, 1);  
}

void				CELL_PSGL_UnmapTextureAsFrameBuffer			()
{
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();

	glBindFramebufferOES(GL_FRAMEBUFFER_OES, 0);
	glViewport(CELL_Video.ScreenConfiguration.Viewport[0], CELL_Video.ScreenConfiguration.Viewport[1], CELL_Video.ScreenConfiguration.Viewport[2], CELL_Video.ScreenConfiguration.Viewport[3]);
}

void								CELL_PSGL_SetUnderscan		(int32_t aX, int32_t aY)
{
	CELL_Video.ScreenConfiguration.UnderscanX = aX;
	CELL_Video.ScreenConfiguration.UnderscanY = aY;

	CalculateViewport();
}

void								CELL_PSGL_GetUnderscan		(int32_t* aX, int32_t* aY)
{
	if(aX)
	{
		*aX = CELL_Video.ScreenConfiguration.UnderscanX;
	}

	if(aY)
	{
		*aY = CELL_Video.ScreenConfiguration.UnderscanY;
	}
}

void								CELL_PSGL_SetVertex			(GLfloat* aBase, float aX, float aY, float aU, float aV)
{
	*aBase++ = aX; *aBase++ = aY; *aBase++ = 0.0f; *aBase++ = aU; *aBase++ = aV;
}

void								CELL_PSGL_SetRectangle		(GLfloat* aBase, float aX, float aY, float aWidth, float aHeight)
{
	CELL_PSGL_SetVertex(aBase + 0,  aX,				aY,				0.0f, 0.0f);
	CELL_PSGL_SetVertex(aBase + 5,  aX + aWidth,	aY,				1.0f, 0.0f);
	CELL_PSGL_SetVertex(aBase + 10, aX + aWidth,	aY + aHeight,	1.0f, 1.0f);
	CELL_PSGL_SetVertex(aBase + 15, aX,				aY + aHeight,	0.0f, 1.0f);
}

GLuint								CELL_PSGL_MakeTexture		()
{
	GLuint id;

	glEnable(GL_TEXTURE_2D);
	glGenTextures(1, &id);
	glBindTexture(GL_TEXTURE_2D, id);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);	
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

	return id;
}

void								CELL_PSGL_KillAttribSetRect	(GLuint* aBufferID, GLuint* aAttributeSet)
{
	glBindAttribSetSCE(0);
	glDeleteAttribSetsSCE(1, aAttributeSet);
	glDeleteBuffers(1, aBufferID);
}

void								CELL_PSGL_MakeAttribSetRect	(GLuint* aBufferID, GLuint* aAttributeSet, float aX, float aY, float aWidth, float aHeight)
{
	MakeAttribSetRect(aBufferID, aAttributeSet, aX, aY, aWidth, aHeight, 0);
}

void								CELL_PSGL_MakeAttribSetRectI(GLuint* aBufferID, GLuint* aAttributeSet, float aX, float aY, float aWidth, float aHeight)
{
	MakeAttribSetRect(aBufferID, aAttributeSet, aX, aY, aWidth, aHeight, 1);
}

/* Implementation */
/* TODO: Is performance really reduced by leaving the buffer mapped ? */
int									CELL_LockHWSurface			(_THIS, SDL_Surface *surface)
{
	surface->pixels = glMapBuffer(GL_TEXTURE_REFERENCE_BUFFER_SCE, GL_READ_WRITE);
	return(0);
}

void								CELL_UnlockHWSurface		(_THIS, SDL_Surface *surface)
{
	surface->pixels = 0;
	glUnmapBuffer(GL_TEXTURE_REFERENCE_BUFFER_SCE);
}

/* TODO: Is this really faster than just jamming the memory with memset ? */
/*       Maybe an SPU could be faster */
int									CELL_FillHWRect				(_THIS, SDL_Surface *dst, SDL_Rect *rect, Uint32 color)
{
	CELL_PSGL_MapTextureAsFrameBuffer(CELL_Video.Shader[0].TextureID, CELL_Video.VideoMode.Width, CELL_Video.VideoMode.Height);

	glEnable(GL_SCISSOR_TEST);
	glScissor(rect->x, dst->h - (rect->y + rect->h), rect->w, rect->h);

	float r = ((color >> 16) & 0xFF) / 255.0f;
	float g = ((color >> 8) & 0xFF) / 255.0f;
	float b = ((color >> 0) & 0xFF) / 255.0f;
	float a = ((color >> 24) & 0xFF) / 255.0f;
	glClearColor(r, g, b, a);
	glClear(GL_COLOR_BUFFER_BIT);

	glDisable(GL_SCISSOR_TEST);

	CELL_PSGL_UnmapTextureAsFrameBuffer();
	return 0;
}

void								CELL_PSGL_Setup				(_THIS)
{
	/* TODO: What are the optimal values for SPUs here */
	/* Initialie PSGL */
	sys_spu_initialize(6, 2);
	PSGLinitOptions initOpts = {PSGL_INIT_MAX_SPUS | PSGL_INIT_INITIALIZE_SPUS | PSGL_INIT_HOST_MEMORY_SIZE, 1, 1, 0, 0, 0, 0, 32 * 1024 * 1024};
	psglInit(&initOpts);

	/* Create PSGL device and context */
	CELL_Video.PSGLState.Device = psglCreateDeviceAuto(GL_ARGB_SCE, GL_NONE, GL_MULTISAMPLING_NONE_SCE);
	CELL_Video.PSGLState.Context = psglCreateContext();
	psglMakeCurrent(CELL_Video.PSGLState.Context, CELL_Video.PSGLState.Device);
	psglResetCurrentContext();

	/* Get screen dimensions and aspect */
	CELL_Video.ScreenConfiguration.AspectRatio = psglGetDeviceAspectRatio(CELL_Video.PSGLState.Device);
	psglGetDeviceDimensions(CELL_Video.PSGLState.Device, &CELL_Video.ScreenConfiguration.Width, &CELL_Video.ScreenConfiguration.Height);

	/* Make a frame buffer object */
	glGenFramebuffersOES(1, &CELL_Video.PSGLState.FrameBufferID);

	/* Make Frame buffer, here we allocate enough room for a 1920x1080 video mode */
	/* this way there is no reason to ever need to allocate it again */
	/* TODO: Can we use GL_SYSTEM_DRAW_SCE here, would it offer any advantages ? */
	glGenBuffers(1, &CELL_Video.PSGLState.TextureBufferID);
	glBindBuffer(GL_TEXTURE_REFERENCE_BUFFER_SCE, CELL_Video.PSGLState.TextureBufferID);
	glBufferData(GL_TEXTURE_REFERENCE_BUFFER_SCE, 1920 * 1080 * 4, 0, GL_STREAM_DRAW);

	/* Setup CG, Stolen from snes9x-ps3*/
#ifdef CELL_SHADER_SUPPORT
	cgRTCgcInit();
	CELL_Video.PSGLState.ShaderContext = cgCreateContext();
#endif
}


void								CELL_PSGL_Kill				(_THIS)
{
	/* TODO: Do I need to actuall glDelete* all of the objects I made ? */
#ifdef CELL_SHADER_SUPPORT
	cgDestroyContext(CELL_Video.PSGLState.ShaderContext);
#endif

	psglDestroyContext(CELL_Video.PSGLState.Context);
	psglDestroyDevice(CELL_Video.PSGLState.Device);

	psglExit();
}

void								CELL_PSGL_Update			(_THIS)
{
	/* Clear the screen */
	glClear(GL_COLOR_BUFFER_BIT);

	/* Present */
	CELL_SHADE_Present();

	/* Draw overlays */
#ifndef CELL_NO_SOFT_INPUT
	CELL_PAD_DrawOverlay();

	if(CELL_Video.Pads.Pads[0].Mode == MOUSE)
	{
		CELL_CURSOR_Draw(0, 0, 0, 1);
	}
#endif
	
	/* Swap screen */
	psglSwap();

	/* Update time */
	CELL_Video.PSGLState.RefreshTime = SDL_GetTicks();
}

int									CELL_PSGL_SetMode			(_THIS, SDL_Surface* aCurrent)
{
	/* TODO: Are these needed? Does it matter? */
	/* Finish */
	glFlush();
	glFinish();

	/* Sanity checks */
	if(CELL_Video.VideoMode.Width > 1920 || CELL_Video.VideoMode.Height > 1080 || CELL_Video.VideoMode.Width == 0 || CELL_Video.VideoMode.Height == 0)
	{
		SDL_SetError("Screen mode is larger than 1920x1080 maximum");
		return -1;
	}

	if(aCurrent->pixels)
	{
		SDL_SetError("Changing screen mode while screen is locked");
		return -1;
	}

	/* Setup view */
	CalculateViewport();
	glViewport(CELL_Video.ScreenConfiguration.Viewport[0], CELL_Video.ScreenConfiguration.Viewport[1], CELL_Video.ScreenConfiguration.Viewport[2], CELL_Video.ScreenConfiguration.Viewport[3]);

	/* Setup projection */
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrthof(0, CELL_Video.VideoMode.Width, CELL_Video.VideoMode.Height, 0, -1, 1);

	/* Update shader */
	CELL_SHADE_UpdateMode();

	/* Finish */
	glFlush();
	glFinish();

	/* TODO: Turn off when single buffered ? */
	glEnable(GL_VSYNC_SCE);

	return 0;
}



